package com.orkun.api.converter.core;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import com.orkun.api.converter.reader.Reader;
import com.orkun.api.converter.writer.Writer;

/**
 * Base class used for conversion. Example usage:
 * 
 * @param <T> an extension of AbsModel class
 * 
 *        <pre>
 * {
 *   Converter&lt;Mode&gt; converter =
 *       ConverterFactory.INSTANCE.createConverter(Model.class, FileTypes.CSV.name(),
 *           FileTypes.XML.name());
 *   converter.addFilter(new Filter(&quot;Afield&quot;, &quot;value&quot;));
 *   converter.convert(instream, outstream);
 * }
 * </pre>
 */
public class Converter<T extends AbsModel> {

  /*
   * A facade pattern variant with subsystems: Reader for parsing from XML, CSV, ..etc Writer for
   * creating XML, CSV, ..etc Sort & Filter executed internally
   */

  ArrayList<T> models = new ArrayList<>();
  private List<Sort> sorts = new ArrayList<>();
  private List<Filter> filters = new ArrayList<>();
  private Reader<T> reader;
  private Writer<T> writer;

  public Converter(Reader<T> r, Writer<T> w) {
    reader = r;
    writer = w;
  }

  /**
   * Converts input with a format to another and returns as output
   * 
   * @param is input data
   * @param os output data
   * @throws Exception error if any
   */
  public final void convert(InputStream is, OutputStream os) throws Exception {

    // if (!validateSchema(filePath))
    // throw new Exception("Invalid file. Conversion failed.");

    DataInputStream din = new DataInputStream(new BufferedInputStream(is));

    models = reader.readData(din);

    // Sort
    for (Sort sort : sorts)
      sort(sort);

    // Filter
    for (Filter filter : filters)
      filter(filter);

    writer.output(os, models);
  }
  
  @SuppressWarnings("unchecked")
  public void filter(Filter filter) {
    try {
      ArrayList<AbsModel> filteredModels = new ArrayList<>();
      for (AbsModel model : models) {
        if ((model).filterBy(filter.getField(), filter.getVal())) {
          filteredModels.add(model);
        }
        models = (ArrayList<T>) filteredModels;
      }
    } catch (Exception e) {
      System.out.println("Error in filter: " + e.getMessage());
    }
  }


  /*
   * Uses a native java implementation of merge sort
   */
  public void sort(final Sort sort) {
    Collections.sort(models, new Comparator<AbsModel>() {
      @Override
      public int compare(AbsModel o1, AbsModel o2) {
        try {
          return o1.compareBy(o2, sort);
        } catch (Exception e) {
          System.out.println("Error in filter: " + e.getMessage());
        }
        return 0;
      }
    });
  }

  /**
   * adds a filter to be applied during the conversion
   * @param filter
   */
  public void addFilter(Filter filter) {
    this.filters.add(filter);
  }

  /**
   * adds a sort to be applied during the conversion
   * @param sort
   */
  public void addSort(Sort sort) {
    this.sorts.add(sort);
  }


}
